package de.urwalking.legobaukasten;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map.Entry;

import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.PointF;
import android.util.Log;
import android.view.MotionEvent;

import com.google.android.gms.drive.internal.l;
import com.threed.jpct.Camera;
import com.threed.jpct.FrameBuffer;
import com.threed.jpct.Interact2D;
import com.threed.jpct.Light;
import com.threed.jpct.Object3D;
import com.threed.jpct.Polyline;
import com.threed.jpct.Primitives;
import com.threed.jpct.RGBColor;
import com.threed.jpct.SimpleVector;
import com.threed.jpct.Texture;
import com.threed.jpct.TextureManager;
import com.threed.jpct.World;
import com.threed.jpct.util.BitmapHelper;
import com.threed.jpct.util.MemoryHelper;
import com.threed.jpct.util.SkyBox;

import de.ralf.urwalking.soplugin.SOHelper;
import de.rj.urwalking.R;
import de.rj.urwalking.data.Building;
import de.rj.urwalking.data.Perspective;
import de.rj.urwalking.data.Pin;
import de.rj.urwalking.data.Poi;
import de.rj.urwalking.data.SalientObject;
import de.rj.urwalking.db.DBSalientObject;
import de.rj.urwalking.ui.BaseRenderer;
import de.rj.urwalking.ui.BaseRenderer.Callbacks;
import de.rj.urwalking.ui.activities.OutdoorActivity;
import de.rj.urwalking.ui.activities.URwalkingActivity;
import de.rj.urwalking.util.CameraUtil;
import de.rj.urwalking.util.PrefUtil;
import de.rj.urwalking.util.Util;

/**
 * This is where all the fancy 3D stuff happens.
 * 
 * @author rjj
 * 
 */

public class LegoRenderer extends BaseRenderer {

	private static final int CAMERA_MAX_Z = 300;
	private static final int CAMERA_MIN_Z = 10;
	private static final RGBColor FLOOR_COLOR = new RGBColor(15, 15, 15);
	private static final RGBColor HIGHLIGHT_COLOR = new RGBColor(0, 128, 0);
	public static final RGBColor POI_COLOR = new RGBColor(200, 200, 200);
	public static final RGBColor BUILDING_COLOR = new RGBColor(100, 100, 100);
	private static final RGBColor SKY_COLOR = new RGBColor(38, 152, 211);

	private Object3D userTriangleRight;
	private Object3D userTriangleLeft;
	private SkyBox skyBox;
	private Object3D floor = null;
	private Object3D previousSelectedObject = null;
	private Light light;
	private SimpleVector lightPosition;

	/** Florian's and Andreas' helper class */
	private SOHelper soHelper = new SOHelper();

	public XMLParser parser;
	public int currentLevel = 1;
	
	private HashMap<Integer, Integer> buildingIds = new HashMap<Integer, Integer>();
	private HashMap<Integer, Integer> poiIds = new HashMap<Integer, Integer>();
	private ArrayList<Integer> removeList;
	
	//Edges&Walls&Pois
	private ArrayList<ArrayList<Polyline>> walls = new ArrayList<ArrayList<Polyline>>();
	private ArrayList<ArrayList<Polyline>> paths = new ArrayList<ArrayList<Polyline>>();
	private ArrayList<ArrayList<Poi>> pois = new ArrayList<ArrayList<Poi>>();

	private boolean wallVisibility = false;
	
	private boolean removeNow = false;

	private enum PoiVisibility {
		ALL, FREQUENT, NONE;
	}

	ArrayList<Pin> userAddedLandmarks = new ArrayList<Pin>();
	private HashMap<Integer, DBSalientObject> dbSalientObjects = new HashMap<Integer, DBSalientObject>();
	private ArrayList<SalientObject> existingLandmarks = new ArrayList<SalientObject>();

	private boolean checkedSalientObjects = false;
	private Object3D userTriangleBottom;

	public LegoRenderer(Context context, Callbacks callbacks, int perspective) {
		super(context);
		this.context = context;
		mCallbacks = callbacks;
		this.perspective = perspective;
	}

	public void onSurfaceChanged(GL10 gl, int w, int h) {
		if (frameBuffer != null) {
			frameBuffer.dispose();
		}
		frameBuffer = new FrameBuffer(w, h);

		loadDefaultTextures();
		
		for(int i = 0; i <= parser.currentLevel; i++)
		{
			paths.add(new ArrayList<Polyline>());
			pois.add(new ArrayList<Poi>());
		}

		if (world == null) {
			setupWorld();

			setupCamera();
			setupLighting();

			addSkyBox();
			addFloor();
			addUserArrow();
			addPois();
			addUserLandmarks();
			
			addPaths();
			addWalls();
		}

		MemoryHelper.compact();
		mCallbacks.onWorldReady();
	}

	private void setupWorld() {
		world = new World();

	}

	protected void setupCamera() {

		camera = world.getCamera();
		camera.setFOVLimits(0.5f, 5f);
		camera.setFOV(PrefUtil.readFov(context));

		if (cameraVectors != null) {
			// we already have a saved position from last time
			camera.setPosition(cameraVectors[0]);
			camera.setOrientation(cameraVectors[1], cameraVectors[2]);
			return;
		}
		switch (perspective) {
		case Perspective.TYPE_3D_STREET:
			camera.moveCamera(Camera.CAMERA_MOVEIN, 1.8f);
			camera.lookAt(SimpleVector.ORIGIN);
			camera.rotateZ((float) Math.PI);
			camera.rotateX((float) Math.PI / 2);
			break;
		case Perspective.TYPE_3D_ABOVE:
			camera.moveCamera(Camera.CAMERA_MOVEIN, 100);
			camera.lookAt(SimpleVector.ORIGIN);
			camera.rotateZ((float) Math.PI);
		default:
			Log.w(TAG, "No Perspective specified!");
		}

	}

	public void onSurfaceCreated(GL10 gl, EGLConfig config) {
	}

	public void onDrawFrame(GL10 gl) {

		// drawDebugIndicators();

		updateCamera();
		updateLighting();
		updateObjects();

		soHelper.removeSalientObjectsFromWorld(world);

		frameBuffer.clear(SKY_COLOR);
		skyBox.render(world, frameBuffer);
		world.renderScene(frameBuffer);
		world.draw(frameBuffer);
		frameBuffer.display();

		logFps();
	}

	public void changeLevel()
	{
		currentLevel++;
		if(currentLevel > parser.currentLevel) currentLevel = 0;
		
		for(int i = 0; i <= parser.currentLevel; i++)
		{
			for(Polyline p : paths.get(i))
			{
				if(i == currentLevel) p.setVisible(true);
				else p.setVisible(false);
			}
			
			for(Polyline p : walls.get(i))
			{
				if(i == currentLevel) p.setVisible(wallVisibility);
				else p.setVisible(false);
			}
			
			for(Poi p : pois.get(i))
			{
				if(i == currentLevel) p.setVisibility(true);
				else p.setVisibility(false);
			}
		}
		
	}
	

	/**
	 * Removes buildings that are marked for removal.
	 */

	private void updateObjects() {

		if (!removeNow) return;

		if (DEBUG) Log.d(TAG, "updateObjects: removeList: " + Util.join(", ", removeList));

		if (DEBUG) Log.d(TAG, world.getSize() + " objects before removal");

		for (int id : removeList) {
			world.removeObject(id);
		}

		if (DEBUG) Log.d(TAG, world.getSize() + " objects after removal");

		removeList = null;
		removeNow = false;
	}

	private void loadDefaultTextures() {

		TextureManager tm = TextureManager.getInstance();

		if (!tm.containsTexture("sky")) {
			tm.addTexture("sky", new Texture(64, 64, SKY_COLOR));
		}
		if (!tm.containsTexture("poi")) {
			Bitmap icon = BitmapHelper.rescale(
					BitmapHelper.convert(context.getResources().getDrawable(R.drawable.poi)), 64, 64);
			Texture texture = new Texture(icon);
			tm.addTexture("poi", texture);
		}

	}

	private void addSkyBox() {
		skyBox = new SkyBox("sky", "sky", "sky", "sky", "sky", "sky", 3000);
	}

	private void addFloor() {
		floor = Primitives.getPlane(64, 25);

		floor.rotateX((float) Math.PI);
		floor.setAdditionalColor(FLOOR_COLOR);
		floor.setCollisionMode(Object3D.COLLISION_CHECK_OTHERS);
		floor.setShadingMode(Object3D.SHADING_FAKED_FLAT);
		floor.strip();
		floor.build();

		world.addObject(floor);

	}
	private void setupLighting() {
		world.setAmbientLight(20, 20, 20);

		light = new Light(world);
		light.setIntensity(150, 150, 150);
		lightPosition = new SimpleVector(0, 0, 100);

	}

	/**
	 * Called when there is a location change in the activity. Updates camera position and user location indicator.
	 * 
	 * @param x
	 * @param y
	 */

	public void moveUserTo(float x, float y) {

		if (camera != null && userArrow != null) {
			floor.setOrigin(SimpleVector.create(x, y, 0f));
			camera.setPosition(SimpleVector.create(x, y, camera.getPosition().z));
			userArrow.setOrigin(SimpleVector.create(x, y, userArrow.getOrigin().z));

			// new CameraUtil.MoveTask(camera, userLocationIndicator).execute(x,
			// y);

		}
	}

	public boolean adjustCameraOverview(MotionEvent event) {

		if (camera == null) {
			return false;
		}

		switch (event.getAction() & MotionEvent.ACTION_MASK) {

		case MotionEvent.ACTION_DOWN:
		case MotionEvent.ACTION_POINTER_DOWN:
			startPoint.set(event.getX(), event.getY());
			mode = DRAG;
			break;
		case MotionEvent.ACTION_MOVE:
			if (mode == DRAG) {
				panCamera(event);
			}
			return true;
		case MotionEvent.ACTION_UP:
		case MotionEvent.ACTION_POINTER_UP:
			mode = NONE;
			return false;
		}
		return false;
	}

	private void panCamera(MotionEvent event) {

		if (startPoint == null) {
			startPoint = new PointF(event.getX(), event.getY());
			return;
		}

		float xd = event.getX() - startPoint.x;
		float yd = event.getY() - startPoint.y;

		// speed up/slow down movement based on camera height (z) and field of
		// view
		float acceleration = (camera.getPosition().z / 1000) * (camera.getFOV() / 1.5f);

		camera.moveCamera(Camera.CAMERA_MOVELEFT, xd * acceleration);
		camera.moveCamera(Camera.CAMERA_MOVEUP, yd * acceleration);

		startPoint.set(event.getX(), event.getY());
	}

	public void zoom(float scaleFactor) {

		if (perspective == Perspective.TYPE_3D_ABOVE) {
			zoomOverview(scaleFactor);
		} else if (perspective == Perspective.TYPE_3D_STREET) {
			zoomInEgoPerspective(scaleFactor);
		}

	}

	private void zoomOverview(float scaleFactor) {
		final float SPEED = 200f;
		camera.moveCamera(Camera.CAMERA_MOVEIN, (scaleFactor - 1) * SPEED);

		if (camera.getPosition().z < CAMERA_MIN_Z) {
			camera.setPosition(camera.getPosition().x, camera.getPosition().y, CAMERA_MIN_Z);
		} else if (camera.getPosition().z > CAMERA_MAX_Z) {
			camera.setPosition(camera.getPosition().x, camera.getPosition().y, CAMERA_MAX_Z);
		}
	}

	public void zoomIn() {
		new CameraUtil.ZoomTask(camera).execute();
	}

	/**
	 * make lights always follow the camera
	 */

	private void updateLighting() {

		lightPosition.x = camera.getPosition().x;
		lightPosition.y = camera.getPosition().y;

		light.setPosition(lightPosition);

	}

	/**
	 * projects a ray from touch coordinates in 3d space and computes collision
	 * 
	 * @param x
	 *            x coordinate where the user touched the screen
	 * @param y
	 *            y coordinate where the user touched the screen
	 * @return the object that was hit or a new one, if we hit the floor
	 */

	public SalientObject checkRayCollision(float x, float y) {

		if (frameBuffer == null || camera == null) return null;

		SimpleVector ray = Interact2D.reproject2D3DWS(camera, frameBuffer, (int) x, (int) y).normalize();

		final int maxTouchDistance = 1000;
		Object[] result = world.calcMinDistanceAndObject3D(camera.getPosition(), ray, maxTouchDistance);

		// return if we didn't hit anything
		if (result[1] == null) {
			return null;
		}

		resetHighlight();

		Object3D o = (Object3D) result[1];

		if (o instanceof Building || o instanceof Poi || o instanceof Pin) {
			// User touched an existing object. Let's highlight and return it.
			o.setAdditionalColor(SKY_COLOR);
			previousSelectedObject = o;
			return (SalientObject) o;
		} else if (o.getID() == floor.getID()) {
			// User touched the floor. First, we have to find the exact coordinates:
			SimpleVector newLandmarkPosition = computeIntersectionWithFloor(ray);
			// Now we can create a new object and return it.
			SalientObject newLandmark = addNewLandmark(newLandmarkPosition);
			userAddedLandmarks.add((Pin) newLandmark);
			previousSelectedObject = newLandmark;

			return newLandmark;

		}
		return null;

	}

	/**
	 * Removes the additional color of the object that was selected before.
	 */

	private void resetHighlight() {

		if (previousSelectedObject == null) {
			return;
		}

		previousSelectedObject.clearAdditionalColor();

		if (previousSelectedObject instanceof Building) {

			if (((Building) previousSelectedObject).wasPreviouslyALandmark()) {
				previousSelectedObject.setAdditionalColor(HIGHLIGHT_COLOR);
			} else {
				previousSelectedObject.setAdditionalColor(BUILDING_COLOR);
			}
		} else if (previousSelectedObject instanceof Poi) {
			previousSelectedObject.setAdditionalColor(POI_COLOR);
		} else if (previousSelectedObject instanceof Pin) {
			previousSelectedObject.setAdditionalColor(PIN_COLOR);
		}

	}

	public SalientObject addLandmarkAtUserPosition() {
		resetHighlight();

		SalientObject landmark = addNewLandmark(userArrow.getOrigin());
		userAddedLandmarks.add((Pin) landmark);
		previousSelectedObject = landmark;

		return landmark;

	}
	
	//lego
	private Polyline createLine(SimpleVector source, SimpleVector sink)
	{
		SimpleVector edge[] = new SimpleVector[]
		{
			source,
			sink
		};
		
		Polyline temp = new Polyline(edge, new RGBColor(130, 180, 100));
		temp.setWidth(15);
		
		return temp;
	}
	
	//lego
	public void addPaths()
	{
		if (world == null)
		{
			Log.d("lego", "addPaths: world null");
			return;
		}
		
		if (parser.URWedges == null)
		{
			Log.d("lego", "addPaths: edges null");
			return;
		}
		
		for(int i = 0; i <= parser.currentLevel; i++)
		{
			for(URWedge e : parser.URWedges.get(i))
			{
				if(e.initialized)
				{
					Polyline pl = createLine(e.source.vector, e.sink.vector);
					paths.get(i).add(pl);
					if(i == currentLevel) pl.setVisible(true);
					else pl.setVisible(false);
					world.addPolyline(pl);
				}
				else
				{
					Log.d("lego", "edge " + e.id + " not initialized");
				}
			}
		}
	}
	
	public void addWalls()
	{
		URWwall urwwall = new URWwall();
		for (int i = 0; i <= parser.currentLevel; i++)
		{
			ArrayList<Polyline> wallList = urwwall.createWalls(parser.URWshops.get(i), parser.URWedges.get(i));
			walls.add(wallList);
			
			for(Polyline e : wallList)
			{
				if (i != currentLevel)
				{
					e.setVisible(false);
				}
				world.addPolyline(e);
			}
		}
	}
	
	
	

	public void addPois()
	{

		if (world == null)
		{
			Log.d("lego", "addPois: world null");
			return;
		}
		
		for(int i = 0; i <= parser.currentLevel; i++)
		{
			for(URWnode n : parser.URWshops.get(i))
			{
				Poi poi = new Poi(n.id, n.x, n.y);
				poi.addTag("amenity", n.type.toLowerCase());
				poi.addName(n.name, n.id);
				poi.applyOffset(new SimpleVector(0,0,0));
				poi.setIcon(context);
				
				poiIds.put(poi.getOsmId(), poi.getID());
				pois.get(i).add(poi);
				
				if(i == currentLevel) poi.setVisibility(true);
				else poi.setVisibility(false);
				
				world.addObject(poi);
			}
			
			for(URWleveledge l : parser.URWleveledges)
			{
				URWnode n;
				if(l.sourceLevel == i && l.initialized) n = l.source;
				else if(l.sinkLevel == i && l.initialized) n = l.sink;
				else break;
				
				Poi poi = new Poi(n.id, n.x, n.y);
				poi.addTag("amenity", l.type.toLowerCase());
				poi.addName(n.name, n.id);
				poi.applyOffset(new SimpleVector(0,0,0));
				poi.setIcon(context);
				
				poiIds.put(poi.getOsmId(), poi.getID());
				pois.get(i).add(poi);
				
				if(i == currentLevel) poi.setVisibility(true);
				else poi.setVisibility(false);
				
				world.addObject(poi);
			}
		}
	}

	public void addBuildings(ArrayList<Building> buildings) {

		if (world == null) return;

		for (Building building : buildings) {
			if (buildingIds.get(building.getGebid()) == null) {
				// building not yet in world
				world.addObject(building);
				buildingIds.put(building.getGebid(), building.getID());
			}
		}
	}

	private void addUserLandmarks() {

		if (world != null) {
			for (SalientObject pin : userAddedLandmarks) {
				world.addObject(pin);
			}
		}
	}

	private boolean doesWorldContainBuilding(int gebid) {
		if (world == null) return false;
		return world.getObjectByName(String.valueOf(gebid)) != null;
	}

	private boolean doesWorldContainPoi(int osmid) {
		if (world == null) return false;
		return world.getObjectByName(String.valueOf(osmid)) != null;
	}

	public void setBuildingsVisibility(boolean isVisible) {

		for (int buildingId : buildingIds.values()) {
			world.getObject(buildingId).setVisibility(isVisible);
		}
	}

	public void setPoiVisibility(boolean isVisible) {
		for (int poiId : poiIds.values()) {
			world.getObject(poiId).setVisibility(isVisible);
		}
	}
	
	public void setWallVisibility() {
		
		wallVisibility = !wallVisibility;
		
		for (Polyline poly : walls.get(currentLevel)) {
			poly.setVisible(wallVisibility);
		}
	}

	public void setPoiVisibility(String poiVisibility) {

		switch (PoiVisibility.valueOf(poiVisibility)) {
		case ALL:
			setPoiVisibility(true);
			break;
		case FREQUENT:
			showFrequentPoisOnly();
			break;
		case NONE:
			setPoiVisibility(false);
			break;
		default:
			setPoiVisibility(false);
			break;
		}
	}

	private void showFrequentPoisOnly() {

		for (int poiId : poiIds.values()) {

			Poi poi = (Poi) world.getObject(poiId);
			if (poi.hasMeaningfulIcon()) {
				poi.setVisibility(true);
			} else {
				poi.setVisibility(false);
			}
		}

	}

	public void setBuildingsOpacity(boolean isTransparent) {

		if (isTransparent) {
			for (int buildingId : buildingIds.values()) {
				world.getObject(buildingId).setCulling(true);
				world.getObject(buildingId).setTransparency(Building.OPACITY_LOW);
				disableBuildingsCollision();
			}
		} else {
			for (int buildingId : buildingIds.values()) {
				world.getObject(buildingId).setCulling(false);
				world.getObject(buildingId).setTransparency(-1);
				enableBuildingsCollision();
			}
		}
	}

	public void disableBuildingsCollision() {

		for (int buildingId : buildingIds.values()) {
			world.getObject(buildingId).setCollisionMode(Object3D.COLLISION_CHECK_NONE);
		}

	}

	public void enableBuildingsCollision() {

		for (int buildingId : buildingIds.values()) {
			world.getObject(buildingId).setCollisionMode(Object3D.COLLISION_CHECK_OTHERS);
		}

	}

	public void switchToStreetView() {
		perspective = Perspective.TYPE_3D_STREET;
		new CameraUtil.LegoFlyDownTask(camera).execute();
	}

	public void switchToOverview() {
		perspective = Perspective.TYPE_3D_ABOVE;
		new CameraUtil.FlyUpTask(camera).execute(100.0);
	}

	/**
	 * Checks if the given coordinates are inside of any of the currently displayed buildings.
	 * 
	 * @param x
	 * @param y
	 * @return true if they are inside, false if not
	 */

	public boolean isInsideBuilding(double x, double y) {

		for (int buildingId : buildingIds.values()) {
			Building building = (Building) world.getObject(buildingId);
			if (building.contains(x, y)) {
				return true;
			}
		}
		return false;
	}

	public void addSalientObjects(ArrayList<DBSalientObject> salientObjects) {

		soHelper.removeSalientObjects(world, dbSalientObjects, existingLandmarks);

		for (DBSalientObject salientObject : salientObjects) {

			if (dbSalientObjects.get(salientObject.getId()) == null) {
				// object not yet loaded
				dbSalientObjects.put(salientObject.getId(), salientObject);
				showSalientObject(salientObject);
			}

		}

	}

	/**
	 * Checks if any of the loaded salient objects are among the displayed buildings/pois.
	 */

	public void checkSalientObjects() {

		if (dbSalientObjects == null) return;

		for (DBSalientObject salientObject : dbSalientObjects.values()) {
			if (!salientObject.isShown()) {
				showSalientObject(salientObject);
			}
		}

		checkedSalientObjects = true;
	}

	private void showSalientObject(DBSalientObject dbSalientObject) {

		if (world == null) return;

		SalientObject salientObject = null;

		if (doesWorldContainBuilding(dbSalientObject.getRefgebid())) {
			int gebid = dbSalientObject.getRefgebid();
			salientObject = (Building) world.getObject(buildingIds.get(gebid));
			salientObject.setAdditionalColor(HIGHLIGHT_COLOR);
		} else if (doesWorldContainPoi(dbSalientObject.getRefpoi())) {
			int osmId = dbSalientObject.getRefpoi();
			salientObject = (Poi) world.getObject(poiIds.get(osmId));
			salientObject.setAdditionalColor(HIGHLIGHT_COLOR);
		} else if (dbSalientObject.getX() != 0 && dbSalientObject.getY() != 0) {
			salientObject = addNewLandmark(new SimpleVector(dbSalientObject.getX(), dbSalientObject.getY(), 0));
			userAddedLandmarks.add((Pin) salientObject);

		}

		if (salientObject != null) {
			salientObject.addName(dbSalientObject.getName(), dbSalientObject.getId());
			existingLandmarks.add(salientObject);
			dbSalientObject.setIsShown(true);
		}
	}

	public void hideLandmarks() {

		for (SalientObject lm : existingLandmarks) {

			if (lm instanceof Pin) {
				world.removeObject(lm);
			} else {
				lm.dontShowAsLandmark();
			}
		}

		for (DBSalientObject salientObject : dbSalientObjects.values()) {
			salientObject.setIsShown(false);
		}

	}

	/**
	 * Checks if any of the buildings or pois is farther away than some threshold and, if so, marks it for removal. The
	 * actual removal is done in the animation loop. This should help to avoid disappearing buildings and to keep a
	 * constantly high frame rate.
	 */

	public void removeFarObjects() {

		if (camera == null) return;

		removeList = new ArrayList<Integer>();
		int removalCount = 0;

		double maxRadius = PrefUtil.readMaxRadius(context);
		SimpleVector camPositionAtGround = SimpleVector.create(camera.getPosition().x, camera.getPosition().y, 0);

		// check buildings
		Iterator<Entry<Integer, Integer>> it = buildingIds.entrySet().iterator();
		while (it.hasNext()) {
			Entry<Integer, Integer> entry = it.next();
			int buildingId = entry.getValue();

			if (isBuildingTooFarAway(buildingId, camPositionAtGround, maxRadius)) {
				removeList.add(buildingId);
				it.remove();
				removalCount++;
			}
		}

		// check pois
		it = poiIds.entrySet().iterator();
		while (it.hasNext()) {
			Entry<Integer, Integer> entry = it.next();
			int poiId = entry.getValue();

			if (isPoiTooFarAway(poiId, camPositionAtGround, maxRadius)) {
				removeList.add(poiId);
				it.remove();
				removalCount++;
			}
		}

		// We may not interfere with the renderer from this method/thread.
		// Instead, we set this flag for the render loop.
		removeNow = true;

		if (DEBUG) Log.d(TAG, "Marked " + removalCount + " objects for removal");

	}

	private boolean isBuildingTooFarAway(int id, SimpleVector camPosition, double maxRadius) {

		Building building = null;
		try {
			building = (Building) world.getObject(id);
		} catch (RuntimeException e) {
			Log.d(TAG, "couldn't find building with id " + id);
			return false;
		}

		float distance = building.getCentroid().distance(camPosition);
		return (distance > maxRadius);
	}

	private boolean isPoiTooFarAway(int id, SimpleVector camPosition, double maxRadius) {

		Poi poi = null;
		try {
			poi = (Poi) world.getObject(id);
		} catch (RuntimeException e) {
			Log.d(TAG, "couldn't find poi with id " + id);
			return false;
		}
		float distance = poi.getTranslation().distance(camPosition);
		return (distance > maxRadius);

	}

	public void lookAt(SimpleVector position) {
		camera.lookAt(position);
		camera.setOrientation(camera.getDirection(), new SimpleVector(0, 0, 1));
	}

}
